/*
 * Created on Jan 19, 2006
 *  
 */
package net.amsoft.iservice.isso.client;

import java.io.BufferedReader;
import java.net.URL;
import java.net.URLEncoder;
import java.rmi.server.UID;
import java.security.PublicKey;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;
import java.security.Security;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;


import org.apache.soap.Body;
import org.apache.soap.Envelope;
import org.apache.soap.Header;
import org.apache.soap.messaging.Message;
import org.apache.soap.transport.SOAPTransport;
import org.apache.soap.util.xml.XMLParserUtils;
import org.apache.xml.security.Init;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xmlbeans.XmlObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import x0Assertion.oasisNamesTcSAML2.AssertionType;
import x0Assertion.oasisNamesTcSAML2.AuthnStatementType;
import x0Assertion.oasisNamesTcSAML2.NameIDType;
import x0Assertion.oasisNamesTcSAML2.SubjectType;
import x0Protocol.oasisNamesTcSAML2.ArtifactDocument;
import x0Protocol.oasisNamesTcSAML2.ArtifactResolveDocument;
import x0Protocol.oasisNamesTcSAML2.ArtifactResolveType;
import x0Protocol.oasisNamesTcSAML2.ArtifactResponseDocument;
import x0Protocol.oasisNamesTcSAML2.ArtifactResponseType;
import x0Protocol.oasisNamesTcSAML2.AuthnRequestDocument;
import x0Protocol.oasisNamesTcSAML2.AuthnRequestType;
import x0Protocol.oasisNamesTcSAML2.ExtensionsType;
import x0Protocol.oasisNamesTcSAML2.LogoutRequestDocument;
import x0Protocol.oasisNamesTcSAML2.LogoutRequestType;
import x0Protocol.oasisNamesTcSAML2.LogoutResponseDocument;
import x0Protocol.oasisNamesTcSAML2.RequestedAuthnContextType;
import x0Protocol.oasisNamesTcSAML2.ResponseDocument;
import x0Protocol.oasisNamesTcSAML2.ResponseType;
import x0Protocol.oasisNamesTcSAML2.StatusResponseType;
import x0Protocol.oasisNamesTcSAML2.AuthnContextComparisonType.Enum;

/**
 * Required libraries: <BR>
 * saml.jar <BR>
 * soap.jar <BR>
 * xbean.jar <BR>
 * xmlsec-1.1.jar
 * 
 * @author rahul
 *  
 */
public class ISSOClient {

    public static final String SAML_STATUS_SUCCESS = "urn:oasis:names:tc:"
            + "SAML:2.0:status:Success";
    public static final String SAML_STATUS_AUTH_FAILED = "urn:oasis:names:tc:"
            + "SAML:2.0:status:AuthnFailed";
    public static final String SAML_STATUS_UNKNOWN_PRINCIPAL = "urn:oasis:"
            + "names:tc:SAML:2.0:status:UnknownPrincipal";
    public static final String SAML_STATUS_RESPONDER = "urn:oasis:names:tc:"
            + "SAML:2.0:status:Responder";
    public static final String REQUEST_REASON = "urn:oasis:names:tc:SAML:2.0:"
            + "logout:user";
    public static final String OASIS_URI = "http:/www.oasis-open.org/"
            + "committees/security";

    public static final String SAML_STATUS_NO_AUTHN_CONTEXT = "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext";
    public static final String SAML_STATUS_REQUESTER = "urn:oasis:names:tc:SAML:2.0:status:Requester";
    public static final String SAML_STATUS_REQUEST_DENIED = "urn:oasis:names:tc:SAML:2.0:status:RequestDenied";
    public static final String SAML_STATUS_REQUEST_UNSUPPORTED = "urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported";
    public static final String SAML_STATUS_NO_AVAILABLE_IDP = "urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP";

    public static final String FRAGMENT_START = "<xml-fragment xmlns:urn="
            + "\"urn:oasis:names:tc:SAML:2.0:protocol\">";
    public static final String FRAGMENT_END = "</xml-fragment>";

    public static final String ENCODING = "UTF-8";
    public static final String PARAM_SAML_REQUEST = "SAMLRequest";
    public static final String PARAM_RELAY_STATE = "RelayState";
    public static final String PARAM_SAML_ART = "SAMLart";

    /**
     * Returns a URI suitable for invoking an authentication/logout request on
     * the given service endpoint (SEP) via HTTP GET. The given SAML
     * authentication/logout request (XML) is the result of calling the
     * Request/createSAMLLogoutRequest method. It will be URL encoded by this
     * operation. Obtain the value for the relay state argument from the HTTP
     * session attribute for the key RelayState.
     * 
     * @param sSEP
     *            Service End point of ISSO
     * @param sSamlRequest
     *            Request param value
     * @param sRelayState
     *            Request param value
     * @return URL
     * @throws Exception
     */
    public static String getRedirectRequestURI(String sSEP,
            String sSamlRequest, String sRelayState) throws Exception {
        String sEncodedSAMLRequest = URLEncoder.encode(sSamlRequest, ENCODING);
        String sEncodedRelayState = URLEncoder.encode(sRelayState, ENCODING);

        return sSEP + "?" + PARAM_SAML_REQUEST + "=" + sEncodedSAMLRequest
                + "&" + PARAM_RELAY_STATE + "=" + sEncodedRelayState;

    }//Method : getRedirectRequestURI

    /**
     * Returns a SAML authentication request document (XML) with the given iname
     * and assertion consumer URI. The assertion consumer will be invoked by the
     * ISSO authentication service after an attempted authentication. The
     * boolean parameter indicates if the SAML authentication request should
     * also include a request for reputation score from the IdP. This return
     * value is suitable for passing to the getRedirectRequestURI method.
     * 
     * @param sIname
     *            Iname for which authn request is to be created
     * @param sAssertionConsumerURI
     *            URI at which Assertion is to be consumed
     * @param sSpIdentifier
     *            ID of the Service sending the authentication request
     * @param bReputation
     *            true: if reputation score is to be requested <BR>
     *            false: if reputation score is NOT to be requested
     * @param sIBrokerName
     *            Name of the provider
     * @param bIsPassive
     *            true: SP indicates interaction is prohibited <BR>
     *            false: No interaction restriction from SP
     * @param sAuthnContext
     *            Authentication context requirements in response the request
     * @param sComparisonType
     *            Optional. Degree of comparison to evaluate the requested
     *            context. Values: "exact", "minimum", "maximum", or "better".
     * @return xml representing AuthnRequestDocument
     * @throws IDPClientException
     *             Thrown when provider name is provided as null
     */
    public static String createSAMLAuthRequest(String sIname,
            String sAssertionConsumerURI, String sSpIdentifier,
            boolean bReputation, String sIBrokerName, boolean bIsPassive,
            String[] arAuthnContext, String sComparisonType)
            throws IDPClientException {
        AuthnRequestDocument oAuthnRequestDoc = null;

        try {

            oAuthnRequestDoc = AuthnRequestDocument.Factory.newInstance();
            AuthnRequestType oAuthnRequest = oAuthnRequestDoc
                    .addNewAuthnRequest();

            oAuthnRequest.setID(generateUniqueID());
            oAuthnRequest.setVersion("2.0");
            oAuthnRequest.setIssueInstant(Calendar.getInstance());
            oAuthnRequest.setAssertionConsumerServiceURL(sAssertionConsumerURI);

            //Provider name should not be null in Authn request
            if (sIBrokerName != null) {
                oAuthnRequest.setProviderName(sIBrokerName);
                oAuthnRequest.setIsPassive(bIsPassive);
            } else {
                throw new IDPClientException(
                        "Authentication request could not be created: Provider "
                                + "name can not be null");
            }

            RequestedAuthnContextType oAuthnContextType = RequestedAuthnContextType.Factory
                    .newInstance();
            
            for (int i=0; i<arAuthnContext.length; i++)
                oAuthnContextType.addAuthnContextClassRef(arAuthnContext[i].trim());

            //Comparison type is optional
            if (sComparisonType != null) {
                Enum oComparisonType = Enum.forString(sComparisonType);
                oAuthnContextType.setComparison(oComparisonType);
            }

            oAuthnRequest.setRequestedAuthnContext(oAuthnContextType);
            SubjectType oSubject = oAuthnRequest.addNewSubject();
            NameIDType oNameId = oSubject.addNewNameID();
            oNameId.setNameQualifier(sIname);
            NameIDType oIssuer = oAuthnRequest.addNewIssuer();
            oIssuer.setStringValue(sSpIdentifier);

            /**
             * check if reputation score is to be requested as well this is
             * essentially for use by IContact Service which need to get the
             * reputation score of the principal This is as long as the ISSO
             * Service is the keeper of the reputation score
             */
            if (bReputation) {
                //reputation score requested
                oAuthnRequest.setAttributeConsumingServiceIndex(1);
            } else {
                //reputation score not requested
                oAuthnRequest.setAttributeConsumingServiceIndex(0);
            }
        } catch (IDPClientException idpce) {
            throw idpce;
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred while creating Authentication request"
                            + e.getMessage());
        }

        return oAuthnRequestDoc.xmlText();

    }//Method : createSAMLAuthRequest

    /**
     * Returns a SAML logout request document (XML) with the given i-name and
     * session ID.
     * 
     * @param sIname
     *            Iname for which logout request is to be created
     * @param sSessionID
     *            Session ID of request
     * @param sSpProvidedID
     *            Service Identifier of the requesting service
     * @param sDestinationURL
     *            URL at which response is to be sent
     * @return Logout request
     */
    public static String createSAMLLogoutRequest(String sIname,
            String sSessionID, String sSpProvidedID, String sDestinationURL)
            throws IDPClientException {
        LogoutRequestType oLogoutRequestType = null;

        try {
            /*
             * Create Logout request document
             */
            LogoutRequestDocument oLogoutRequestDoc = LogoutRequestDocument.Factory
                    .newInstance();
            oLogoutRequestType = oLogoutRequestDoc.addNewLogoutRequest();
            oLogoutRequestType.setIssueInstant(Calendar.getInstance());
            oLogoutRequestType.setVersion("2.0");
            oLogoutRequestType.setID(generateUniqueID());
            NameIDType oIssuer = NameIDType.Factory.newInstance();
            oIssuer.setStringValue(sSpProvidedID);
            oLogoutRequestType.setIssuer(oIssuer);
            oLogoutRequestType.addSessionIndex(sSessionID);
            oLogoutRequestType.setDestination(sDestinationURL);
            NameIDType oNameIDType = NameIDType.Factory.newInstance();
            oNameIDType.setNameQualifier(sIname);
            oLogoutRequestType.setNameID(oNameIDType);
            oLogoutRequestType.setReason(REQUEST_REASON);
            return oLogoutRequestDoc.xmlText();
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred while creating Logout request"
                            + e.getMessage());
        }

    }//Method : createSAMLLogoutRequest

    /**
     * Returns a SAML artifact resolve request document (XML) with the given
     * artifact.
     * 
     * @param sArtifact
     *            Artifact is received by the SP from the IdP after the SP sends
     *            Authentication or Logout Request to the IdP.
     * @return ArtifactResolveDocument's xmltext
     * @throws IDPClientException
     *             Thrown when Artifact resolve request could not be created
     */
    public static String createSAMLArtifactResolveRequest(String sArtifact)
            throws IDPClientException {
        String sRetVal = null;

        try {
            //Create ArtifactResolveDocument
            ArtifactResolveDocument oArtResolveDoc = ArtifactResolveDocument.Factory
                    .newInstance();
            ArtifactResolveType oArtResolveType = oArtResolveDoc
                    .addNewArtifactResolve();
            oArtResolveType.setIssueInstant(Calendar.getInstance());
            NameIDType oIssuer = NameIDType.Factory.newInstance();
            oArtResolveType.setIssuer(oIssuer);
            oArtResolveType.setID(generateUniqueID());
            oArtResolveType.setArtifact(sArtifact);
            oArtResolveType.setVersion("2.0");
            sRetVal = oArtResolveDoc.xmlText();
            return sRetVal;
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred while creating artifact resolve request "
                            + "document" + e.getMessage());
        }

    }//Method : createSAMLArtifactResolveRequest

    /**
     * Returns Status Code of the SAML logout response <BR>
     * document contained in the SAML artifact response document returned <BR>
     * by ISSO Service in a SOAP envelope
     * 
     * @param sSAMLArtifactResponse
     *            Document returned by ISSO Service in a SOAP envelope
     * @return Status Code of the SAML logout response
     * @throws IDPClientException
     *             Thrown when artifact response could not be parsed
     */
    public static String getLogoutSatusFromArtifactResponse(
            String sSAMLArtifactResponse) throws IDPClientException {
        try {
            //Parse artifact docuemnt to extract logout status
            ArtifactResponseDocument oArtRespDoc = ArtifactResponseDocument.Factory
                    .parse(sSAMLArtifactResponse);
            ArtifactResponseType oArtResponseType = oArtRespDoc
                    .getArtifactResponse();
            ExtensionsType oExtType = oArtResponseType.getExtensions();
            LogoutResponseDocument oLogoutRespDoc = LogoutResponseDocument.Factory
                    .parse(oExtType.xmlText());
            String sStatus = oLogoutRespDoc.getLogoutResponse().getStatus()
                    .getStatusCode().getValue();
            return sStatus;
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred while extracting logout status from "
                            + "Artifact Response" + e.getMessage());
        }
    }//Method : getLogoutSatusFromArtifactResponse

    /**
     * Returns true if (1) the signature attached to the given SAML response XML
     * is valid, or (2) there is no signature present. The SAML response XML is
     * typically obtained by decoding the SAMLRequest parameter on the
     * AssertionConsumerServlet's HTTP request parameters. (See the SAMLRequest
     * regarding the AssertionConsumerServlet.) An exception is thrown if the
     * given XML does not parse to the expected SAML <Response>format.
     * 
     * @param sSamlResponseXML
     *            SAML response
     * @return true: the signature attached to the given SAML response XML is
     *         valid <BR>
     *         false: there is no signature present
     * @throws IDPClientException
     */
    public static boolean validateSignatureForSAMLResponse(
            String sSamlResponseXML) throws IDPClientException {
        boolean bRetVal = false;

        try {
            XmlObject oDocObject = XmlObject.Factory.parse(sSamlResponseXML);
            Node oW3node = oDocObject.newDomNode();
            Document oW3doc = null;
            Element oElem = null;
            if (oW3node instanceof Document) {
                oW3doc = (Document) oW3node;
                oElem = oW3doc.getDocumentElement();
            } else {
                /**
                 * Ok, much weirdness here -- the owner document exists, and is
                 * registered as our owner, but doesn't actually know about the
                 * child; fix that
                 */
                oW3doc = (oW3node).getOwnerDocument();
                oElem = (Element) (oW3node).getFirstChild();
                oW3doc.appendChild(oW3node);
            }

            DocumentBuilderFactory oDbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder oDb = oDbf.newDocumentBuilder();

            // Get the signed element
            Element oSigElement = (Element) oW3doc.getElementsByTagNameNS(
                    "http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
            Document oTempDoc = oDb.newDocument();
            oTempDoc.appendChild(oTempDoc.importNode(oSigElement
                    .getParentNode(), true));
            NodeList oList = oTempDoc.getElementsByTagNameNS(
                    "http://www.w3.org/2000/09/xmldsig#", "Signature");
            if (oList.getLength() == 0) {
                //Signature not found
                throw new IDPClientException("Signature not present in the "
                        + "document");
            }
            oSigElement = (Element) oList.item(0);
            Init.init();
            XMLSignature oSig = new XMLSignature(oSigElement, "");
            PublicKey oPk = oSig.getKeyInfo().getPublicKey();
            bRetVal = oSig.checkSignatureValue(oPk);

            return bRetVal;
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred while verifying signature. "
                            + e.getMessage());
        }
    }//Method : validateSignatureForSAMLResponse

    /**
     * 
     * @param sArtifactResponseXML
     * @return instance of SAMLDataObject with following data filled <BR>
     *         reputationScore containing reputation score received in <BR>
     *         response, if no reputation score was sent then it is set to -1
     *         <BR>
     *         iname as subjectNameIDQualifier <BR>
     *         sessionID as sessionIndex <BR>
     *         status of the response as SAMLStatus <BR>
     *         note: if status is SAML_STATUS_SUCCESS then only there is <BR>
     *         surety of other data being present
     * @throws IDPClientException
     *             Thrown when destination of the SAML Authn Response is not
     *             same as what is sent as parameter or when status is unknown
     */
    public static SAMLDataObject getAuthnResponseValuesFromArtifactResponse(
            String sArtifactResponseXML) throws IDPClientException {
        try {
            ArtifactResponseDocument oArtResponseDoc = ArtifactResponseDocument.Factory
                    .parse(sArtifactResponseXML);
            SAMLDataObject oSAMLData = new SAMLDataObject();
            ArtifactResponseType oArtResponseType = oArtResponseDoc
                    .getArtifactResponse();
            ExtensionsType oExtType = oArtResponseType.getExtensions();
            String sXmlRespDoc = oExtType.xmlText();

            if (sXmlRespDoc.startsWith(FRAGMENT_START)) {
                int iBeginIndex = sXmlRespDoc.indexOf(FRAGMENT_START)
                        + FRAGMENT_START.length();
                int iEndIndex = sXmlRespDoc.lastIndexOf(FRAGMENT_END);
                sXmlRespDoc = sXmlRespDoc.substring(iBeginIndex, iEndIndex);

            }
            ResponseDocument oRespDoc = ResponseDocument.Factory
                    .parse(sXmlRespDoc);
            ResponseType oResponse = oRespDoc.getResponse();
            String sActualDestination = oResponse.getDestination();
            //			if (!destination.equals(actualDestination))
            //			{
            //				throw new IDPClientException(
            //						"Host is not the desired destination");
            //			}
            String sStatus = oResponse.getStatus().getStatusCode().getValue();
            if (sStatus.equals(SAML_STATUS_SUCCESS)) {
                /**
                 * Validate that the Response XML has the expected elements and
                 * attributes. The validations here describe constraints on an
                 * IDP-returned SAML response. These constraints have been
                 * defined ad-hoc, and need to be based on a document. Where is
                 * that document?
                 */
                AssertionType[] oAssertions = oResponse.getAssertionArray();
                if (oAssertions.length != 1) {
                    throw new IDPClientException(
                            "Expecting exactly one assertion for in SAML response.");
                }
                SubjectType oSubject = oAssertions[0].getSubject();
                if (oSubject == null) {
                    throw new IDPClientException(
                            "Expected <Subject> element in SAML Response.");
                }
                NameIDType oNameID = oSubject.getNameID();
                if (oNameID == null) {
                    throw new IDPClientException(
                            "Expected <NameID> element in SAML Response.");
                }
                String sNameQualifier = oNameID.getNameQualifier();
                if (sNameQualifier == null) {
                    throw new IDPClientException(
                            "Expected NameQualifier attribute in SAML Response.");
                }
                AuthnStatementType[] oAuthStatementArray = oAssertions[0]
                        .getAuthnStatementArray();
                if (oAuthStatementArray == null
                        || oAuthStatementArray.length != 1) {
                    throw new IDPClientException(
                            "Expected exactly one <AuthnStatement> in SAML Response.");
                }
                try {
                    XmlObject oXmlObj = oAssertions[0]
                            .getAttributeStatementArray()[0]
                            .getAttributeArray()[0].getAttributeValueArray(0);
                    Document oDoc = XMLParserUtils.getXMLDocBuilder().parse(
                            oXmlObj.newInputStream());
                    oSAMLData.setReputationScore((Integer.parseInt(oDoc
                            .getDocumentElement().getFirstChild()
                            .getNodeValue())));
                } catch (Exception e) {
                    //no reputation score
                    oSAMLData.setReputationScore(-1);
                }

                // Set the output arguments for iname, sessionID and reputation
                // score (if any).
                oSAMLData.setSAMLStatus(sStatus);
                oSAMLData.setSubjectNameIDNameQualifier(sNameQualifier);
                oSAMLData.setSessionIndex(oAuthStatementArray[0]
                        .getSessionIndex());
            } else if (sStatus.equals(SAML_STATUS_UNKNOWN_PRINCIPAL)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_UNKNOWN_PRINCIPAL);
            } else if (sStatus.equals(SAML_STATUS_AUTH_FAILED)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_AUTH_FAILED);
            } else if (sStatus.equals(SAML_STATUS_NO_AUTHN_CONTEXT)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_NO_AUTHN_CONTEXT);
            } else if (sStatus.equals(SAML_STATUS_REQUESTER)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_REQUESTER);
            } else if (sStatus.equals(SAML_STATUS_RESPONDER)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_RESPONDER);
            } else if (sStatus.equals(SAML_STATUS_REQUEST_DENIED)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_REQUEST_DENIED);
            } else if (sStatus.equals(SAML_STATUS_REQUEST_UNSUPPORTED)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_REQUEST_UNSUPPORTED);
            } else if (sStatus.equals(SAML_STATUS_NO_AVAILABLE_IDP)) {
                oSAMLData.setSAMLStatus(SAML_STATUS_NO_AVAILABLE_IDP);
            } else {
                throw new IDPClientException(
                        "Unknown status type processing SAML Response: "
                                + sStatus);
            }
            return oSAMLData;
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred processing Artifact Response: "
                            + e.getMessage());
        }
    }//Method : getAuthnResponseValuesFromArtifactResponse

    /**
     * Parses the given SAML logout response XML and sets the output arguments
     * accordingly. Note that the SAML response contains a Status. See the
     * "saml-core-2.0-os" document for general information about SAML requests
     * and responses.
     * <p>
     * The SAML logout response provided here is the result of submitting a SAML
     * request obtained by calling the createSAMLLogoutRequest operation. In
     * case of a POST-POST profile, it is typically obtained by decoding the
     * PARAM_NAME_SAML_RESPONSE parameter on the AssertionConsumerServlet's HTTP
     * request parameters. In case of REDIRECT-ARTIFACT profile, it is obtained
     * from the SAML artifact response received over a SOAP back channel.
     * getSAMLLogoutResponseFromSAMLArtifactResponse returns the SAML logout
     * response in case the latter profile is used.
     * <p>
     * The value of destination element of the SAML Authentication Response must
     * match the host Service Provider before further processing of the
     * Response.
     * <p>
     * The caller should carefully follow the instructions here to use the
     * output arguments to determine if the given SAML logout response
     * successfully completed a given logout request.
     * <p>
     * <b>Important: </b> A SAML logout response may not necessarily be signed
     * by the IdP. This operation does not validate the signature attached to
     * the given SAML response. If the response if signed, the
     * verifySignatureForSAMLResponse should always be called prior to invoking
     * this operation.
     * <p>
     * SAML_STATUS_SUCCESS is returned if the logout happenned successessfully
     * (that is, if the given XML's StatusCode element equals
     * "SAML:2.0:status:Success".). If the returned value argument equals
     * SAML_STATUS_RESPONDER (that is if the given XML's StatusCode element
     * equals "urn:oasis:names:tc:SAML:2.0:status:Responder"), logout did not
     * happen successfully at the IDP. This could be either due to an error at
     * the IDP or refusal by the user at the IDP to do a logout (most likely).
     * If the returned value argument equals SAML_STATUS_NO_AUTHN_CONTEXT (that
     * is if the given XML's StatusCode element equals.
     * "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext"), logout did not
     * happen successfully at the IDP. An exception is thrown if the given XML
     * does not parse to the expected XML Response format.
     * 
     * @param sSamlLogoutResponseXML
     *            XML string received from logout response
     * @param sDestination
     *            For authenticating host and actual destination
     * @return Status of logout response
     * @throws Exception
     */
    public static String getValuesFromSAMLLogoutResponse(
            String sSamlLogoutResponseXML, String sDestination)
            throws Exception {
        try {
            LogoutResponseDocument oResponseDoc = LogoutResponseDocument.Factory
                    .parse(sSamlLogoutResponseXML);
            StatusResponseType oResponse = oResponseDoc.getLogoutResponse();
            String sActualDestination = oResponse.getDestination();
            if (!sDestination.equals(sActualDestination)) {
                throw new IDPClientException(
                        "Host is not the desired destination");
            }

            String sStatus = oResponse.getStatus().getStatusCode().getValue();
            if (sStatus.equals(SAML_STATUS_SUCCESS)) {
                return SAML_STATUS_SUCCESS;
            } else if (sStatus.equals(SAML_STATUS_NO_AUTHN_CONTEXT)) {
                return SAML_STATUS_NO_AUTHN_CONTEXT;
            } else if (sStatus.equals(SAML_STATUS_RESPONDER)) {
                return SAML_STATUS_RESPONDER;
            } else {
                throw new IDPClientException(
                        "Unknown status type processing SAML Response: "
                                + sStatus);
            }
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred processing Logout Response: "
                            + e.getMessage());
        }
    }//Method : getValuesFromSAMLLogoutResponse

    /**
     * Parses the SAML artifact received over HTTP GET from the IdP and sets its
     * output arguments accordingly. See the "saml-core-2.0-os" document for
     * general information about SAML requests and responses.
     * <p>
     * The SAML artifact provided here is the result of submitting a SAML
     * request obtained by calling the createSAMLAuthRequest or
     * createSAMLLogoutRequest operation.
     * <p>
     * The providerID is part of the SAML artifact. The messageID is the
     * complete artifact string within the artifact document.
     * 
     * @param sSamlArtifactXML
     *            Artifact Document's XML
     * @return SAMLDataObject instance with following data: <BR>
     *         <B>artifact </B>in the SAMLDataObject will contain the artifact
     *         <BR>
     *         sent in ArtifactDocument <BR>
     *         <B>providerID </B>will contain the providerID part of the
     *         Artifact <BR>
     * @throws Exception
     */
    public static SAMLDataObject getValuesFromSAMLArtifact(
            String sSamlArtifactXML) throws Exception {
        SAMLDataObject oSAMLData = new SAMLDataObject();
        try {
            ArtifactDocument oArtifactDoc = ArtifactDocument.Factory
                    .parse(sSamlArtifactXML);
            oSAMLData.setArtifact(oArtifactDoc.getArtifact());
            oSAMLData.setProviderIDHash(new String(oArtifactDoc.getArtifact()
                    .getBytes(), 32, 28));
        } catch (Exception e) {
            throw new IDPClientException(
                    "Error occurred processing SAML Artifact: "
                            + e.getMessage());
        }

        return oSAMLData;
    }//Method : getValuesFromSAMLArtifact

    /**
     * Extracts artifact response from the SOAP envelope received and returns
     * its XML text
     * 
     * @param oEnvelope
     *            SOAP Envelope received
     * @return Artifact response XML
     */
    public static String getArtifactResponseFromSOAPEnvelpe(Envelope oEnvelope) {
        try {
            //Extract artifact response
            Body oBody = oEnvelope.getBody();
            Vector oBodyEntries = oBody.getBodyEntries();
            Element oElement = null;
            for (Enumeration e = oBodyEntries.elements(); e.hasMoreElements();) {
                oElement = (Element) e.nextElement();
                break;
            }

            //convert artifact response into XML
            String sArtifactResponseXML = XmlObject.Factory.parse(
                    (Node) oElement).xmlText();

            return sArtifactResponseXML;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * function used to send a SOAP envelope over HTTPS, and get back another
     * envelope <BR>
     * in response <BR>
     * ArtifactResolveDocument is sent in SOAP envelope, and <BR>
     * ArtifactResponseDocument is received in return
     * 
     * @param oEnvelope
     *            SOAP envelope to be sent
     * @param sURL
     *            URL where soap envelope needs to be sent
     * @param sKeyStorePath
     *            Path of key store on server
     * @param sKeyStorePwd
     *            Keystore password
     * @return SOAP envelope
     */
    public static Envelope sendAndReceiveSOAPEnvelope(Envelope oEnvelope,
            String sURL, String sKeyStorePath, String sKeyStorePwd) {
        //TO Be removed
        try {
            /* not required for CA valid certs */
            System.setProperty("javax.net.ssl.keyStore", sKeyStorePath);
            System.setProperty("javax.net.ssl.trustStore", sKeyStorePath);
            System.setProperty("javax.net.ssl.keyStorePassword", sKeyStorePwd);
            /* until; here */

            return sendAndReceiveSOAPEnvelope(oEnvelope, sURL);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * function used to send a SOAP envelope, and get back another envelope <BR>
     * in response <BR>
     * ArtifactResolveDocument is sent in SOAP envelope, and <BR>
     * ArtifactResponseDocument is received in return
     * 
     * @param oEnvelope
     *            SOAP envelope to be sent
     * @param sURL
     *            URL where soap envelope needs to be sent
     * @return SOAP envelope
     */
    public static Envelope sendAndReceiveSOAPEnvelope(Envelope oEnvelope,
            String sURL) {
        try {
            System.setProperty("java.protocol.handler.pkgs",
                    "com.sun.net.ssl.internal.www.protocol");
            Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());

            //Send message
            Message oMessage = new Message();
            oMessage.send(new URL(sURL), OASIS_URI, oEnvelope);

            //Receive document
            SOAPTransport oSOAPTransport = oMessage.getSOAPTransport();
            BufferedReader oReader = oSOAPTransport.receive();
            DocumentBuilder oDocumentBuilder = XMLParserUtils
                    .getXMLDocBuilder();
            Document oDocument = oDocumentBuilder
                    .parse(new InputSource(oReader));

            if (oDocument == null) {
                throw new IDPClientException(
                        "SOAP resposne could not be parsed");
            }

            return Envelope.unmarshall(oDocument.getDocumentElement());
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Generates an identifier unique to this JVM at this time of running.
     */
    private static String generateUniqueID() {
        return (new UID().toString() + Float.toString(new Random().nextFloat()));
    }//Method : generateUniqueID

    /**
     * Creates SOAP envelop from the SArtifact resolve document xml
     * 
     * @param sXmlArtResDoc
     *            Artifact resolve document xml
     * @return SOAP envelop comprising Artifact resolve document
     */
    public static final Envelope createSOAPEnvelope(String sXmlArtResDoc) {

        Envelope oEnvelope = null;
        try {
            ArtifactResolveDocument oArtResolveDoc = ArtifactResolveDocument.Factory
                    .parse(sXmlArtResDoc);
            DocumentBuilder oXdb = XMLParserUtils.getXMLDocBuilder();
            Document oDoc = oXdb.newDocument();
            oEnvelope = new Envelope();
            Header oHeader = new Header();
            Vector oBodyEntries = new Vector();
            XmlObject oDocObj = oArtResolveDoc;
            Node oW3node = oDocObj.newDomNode();
            Element oElement;

            if (oW3node instanceof Document) {
                oDoc = (Document) oW3node;
                oElement = oDoc.getDocumentElement();
            } else {
                /**
                 * Ok, much weirdness here -- the owner document exists, and is
                 * registered as our owner, but doesn't actually know about the
                 * child; fix that
                 */
                oDoc = (oW3node).getOwnerDocument();
                oElement = (Element) (oW3node).getFirstChild();
                oDoc.appendChild(oW3node);
            }

            oBodyEntries.add(oElement);

            //Envelope body
            Body oBody = new Body();

            //Set body entries in the body
            oBody.setBodyEntries(oBodyEntries);

            //Set body in the envelope
            oEnvelope.setBody(oBody);

        }//try
        catch (Exception e) {
            return null;
        }//catch

        return oEnvelope;
    }//createSOAPEnvelope() exit

    /**
     * IDPClient exception class.
     */
    public static class IDPClientException extends Exception {
        IDPClientException(String message) {
            super(message);
        }
    }

}//Class : ISSOClient
